%%%-------------------------------------------------------------------
%%% @author Administrator
%%% @copyright (C) 2019, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 23. 12月 2019 11:05
%%%-------------------------------------------------------------------
-module(player).
-author("Administrator").

-behaviour(gen_server).

-include("include/player.hrl").
-include("include/myshare.hrl").
-include("include/logger.hrl").
-include("include/protocol.hrl").
-include("include/game.hrl").
%% API
-export([start_link/1, weiniu_queue/3]).

%% gen_server callbacks
-export([init/1,
  handle_call/3,
  handle_cast/2,
  handle_info/2,
  terminate/2,
  code_change/3]).

-export([ws_close/1]).

-define(SERVER, ?MODULE).

-record(state, {wsConnPid}).

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link(_Args) ->
  {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link(Args) ->
  gen_server:start_link(?MODULE, Args, []).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore |
%%                     {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec(init(Args :: term()) ->
  {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} |
  {stop, Reason :: term()} | ignore).
init(PlayerRecord) ->
  ?DEBUG("add player init:account:~p;password:~p;token:~p;task_id:~p", [PlayerRecord#player_info.account
    , PlayerRecord#player_info.password, PlayerRecord#player_info.token, PlayerRecord#player_info.task_id]),
  #player_info{account = Account, password = Password, token = Token, task_id = TaskId} = PlayerRecord,
  %初始化玩家状态
  erlang:put(?PLAYER_ONLINE_STATUS, ?PLAYER_STATUS_NONE),
  Pid = list_to_atom("pid_" ++ Account),
  register(Pid, self()),

  %初始化网关
  {ok, ConnPid} = init_webSocket(Token, "0"),
  ets:insert(?ETS_PLAYER_PROTOCOL, #player_protocol{account = Account, lobby = ConnPid}),
  ?INFO("conn ws account:~p;connPid:~p", [Account, ConnPid]),
  erlang:put(?PLAYER_WS_CONN_PID, ConnPid),
  LoginTime = sys_util:timestamp(),

  %%存储用户信息到ets
  PlayerInfo = PlayerRecord#player_info{
    pid = self(),
    ws_connPid = ConnPid,
    login_time = LoginTime
  },
  ets:insert(?ETS_PLAYER_INFO, PlayerInfo),
  Is_look_bet = game_assist:player_is_look_bet(TaskId, Account),
  State = #player_state{ws_connPid = ConnPid, token = Token, account = Account, password = Password,
    pid = self(), task_id = TaskId, is_look_bet = Is_look_bet},
  erlang:put(?PLAYER_STATE, State),
  %%登陆5秒后执行循环任务
  erlang:send_after(5000, self(), {timer_Mins_update}),
  %%一分钟取一次房间数据
  erlang:send_after(1000 * 60, self(), {timer_ten_mins}),
  {ok, State}.


%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
    State :: #state{}) ->
  {reply, Reply :: term(), NewState :: #state{}} |
  {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
  {noreply, NewState :: #state{}} |
  {noreply, NewState :: #state{}, timeout() | hibernate} |
  {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
  {stop, Reason :: term(), NewState :: #state{}}).

%%排队
handle_call({queue}, _From, State) ->
  ?INFO("queue State:~p", [State]),
  %存储ets
  Reply =
    case player_assist:player_queue(State#player_state.room_id, State#player_state.id) of
      normal -> error;
      _Index -> {ok, 0} %%排队座位号默认0
    end,
  {reply, Reply, State};

handle_call({player_state}, _From, State) ->
  {reply, State, State};

%%更新玩家状态
handle_call({update_state, Type}, From, State) ->
  gen_server:reply(From, ok),
  NewState =
    case Type of
      {into_room} -> State#player_state{is_room = true};
      {out_room} -> State#player_state{is_room = false};
      _ -> State
    end,
  {noreply, NewState};

handle_call({stop}, _From, State) ->
  {stop, normal, noreply, State};

handle_call({get_login_state}, _From, State) ->
  Login_state = erlang:get(?IS_LOGOUT),
  Killing = erlang:get(?KILLING),
  {reply, {Login_state, Killing}, State};

handle_call({put_login_state}, _From, State) ->
  erlang:put(?IS_LOGOUT, true),
  {reply, ok, State};


handle_call(_Request, _From, State) ->
  {reply, ok, State}.


%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_cast(Request :: term(), State :: #state{}) ->
  {noreply, NewState :: #state{}} |
  {noreply, NewState :: #state{}, timeout() | hibernate} |
  {stop, Reason :: term(), NewState :: #state{}}).
handle_cast({stop}, State) ->
  {stop, normal, State};
handle_cast(_Request, State) ->
  ?INFO("player other handle_cast:~p", [State]),
  {noreply, State}.


%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
  {noreply, NewState :: #state{}} |
  {noreply, NewState :: #state{}, timeout() | hibernate} |
  {stop, Reason :: term(), NewState :: #state{}}).

%%修改结算离桌后直接退出游戏
handle_info({kicking}, State) ->
  case State#player_state.is_room of
    true ->
      erlang:put(?IS_LOGOUT, true),
      erlang:put(?KILLING, true),
      self() ! {game, {checkout}};
    _ ->
      gen_server:cast(self(), {stop})
  end,
  {noreply, State};

%% 用户ws大厅服务器请求
handle_info({send_ws, Code}, State) ->
  send_ws(Code),
  {noreply, State};

%%接收服务器发送的webSocket消息
handle_info({gun_ws, FormId, _Ref, Msg}, State) ->
  NewState =
    case Msg of
      {text, MsgData} ->
        ws_message:message_response(FormId, MsgData, State),
        State;
      {close, _, _} ->
        ?ERROR("close ws connection account:~p", [State#player_state.account]),
        State;
      _ ->
        ?ERROR("player handle_info gun_ws msg:~p", [Msg]),
        case init_webSocket(State#player_state.token, "1") of
          {ok, WsPid} ->
            State#player_state{ws_connPid = WsPid};
          _ -> State
        end
    end,
  {noreply, NewState};

handle_info({gun_down, FormId, _Protocol, _Reason, _Killed, _Unprocessed}, State = #player_state{ws_connPid = Conn_pid}) when FormId =:= Conn_pid ->
  ?ERROR("player gun_down Connection lost, reconnect,formid:~p;_Reason:~p", [FormId, _Reason]),
  self() ! {reconnect},
  {noreply, State};

handle_info({gun_error, FormId, _Reason}, State = #player_state{ws_connPid = Conn_pid}) when FormId =:= Conn_pid ->
  ?ERROR("player gun_error formid:~p,_Reason:~p,account:~p",
    [FormId, _Reason, State#player_state.account]),
  self() ! {reconnect},
  {noreply, State};

handle_info({gun_error, FormId, _StreamRef, _Reason}, State = #player_state{ws_connPid = ConnPid}) when FormId =:= ConnPid ->
  ?ERROR("player other gun_errror reason:~p;_StreamRef:~p,account:~p", [_Reason, _StreamRef, State#room_status.account]),
  self() ! {reconnect},
  {noreply, State};

handle_info({reconnect}, State) ->
  ?INFO("websocket reconnect account:~p", [State#player_state.account]),
  {ok, Ws_pid} = init_webSocket(State#player_state.token, "1"),
  {noreply, State#player_state{ws_connPid = Ws_pid}};

%%启动一个游戏
handle_info({start_game, GameId, RoomId, RoomType}, State) ->
  PlayerInfo = player_assist:get_at_player_info(),
  NewState = State#player_state{id = PlayerInfo#player_info.id, game_id = GameId, room_id = RoomId,
    room_type = RoomType},
  ?DEBUG("start new game :gameid:~p;state:~p", [GameId, NewState]),
  {ok, Pid} = add_game_child(GameId, NewState),
  NewState2 = NewState#player_state{game_pid = Pid},
  erlang:put(?PLAYER_STATE, NewState2),
  ?INFO("start_game State:~p", [NewState2]),
  {noreply, NewState2};

%%关闭游戏
handle_info({stop_game}, State) ->
  GamePid = State#player_state.game_pid,
  gen_server:cast(GamePid, {stop}),
  {noreply, State};

%%进入房间
handle_info({into_game_room}, State) ->
  ?DEBUG("next task into_game_room state :~p", [State]),
  NewState =
    case weiniu_queue(State#player_state.room_id, State#player_state.game_id, State#player_state.account) of
      false ->
        case State#player_state.is_room of
          false ->
            GamePid = State#player_state.game_pid,
            RoomId = State#player_state.room_id,
            GamePid ! {into_game_room, RoomId};
          _ ->
            ?ERROR("It's already in the room account:~p,is_room:~p", [State#player_state.account, State#player_state.is_room])
        end,
        State#player_state{is_room = true};
      _ ->
        State#player_state{is_room = false}
    end,
  erlang:put(?PLAYER_STATE, NewState),
  {noreply, NewState};

handle_info({into_game_room, RoomId}, State) ->
  ?DEBUG("into_game_room:roomid:~p;is_room:~p", [RoomId, State#player_state.is_room]),
  NewState =
    case weiniu_queue(RoomId, State#player_state.game_id, State#player_state.account) of
      false ->
        case State#player_state.is_room of
          false ->
            GamePid = State#player_state.game_pid,
            GamePid ! {into_game_room, RoomId};
          _ ->
            ?ERROR("It's already in the room is_room:~p", [State#player_state.is_room])
        end,
        State#player_state{is_room = true, room_id = RoomId};
      _ ->
        State#player_state{is_room = false}
    end,
  erlang:put(?PLAYER_STATE, NewState),
  {noreply, NewState};


handle_info({update_task, TaskId}, State) ->
  NewState = State#player_state{task_id = TaskId},
  {noreply, NewState};

%%执行下一个任务
handle_info({next_task}, State) ->
  try
    player_assist:next_task(State)
  catch
    Class:Error:Stacktrace ->
      ?ERROR("player timer_Mins_update class:~p;error:~p;stacktrace:~p", [Class, Error, Stacktrace])
  end,
  {noreply, State};

%%每1分钟获取房间数据
handle_info({timer_ten_mins}, State) ->
  ?DEBUG("timer_ten_mins get room data"),
  %%验证任务是否过期
  check_task_overdue(State#player_state.task_id),
  send_ws(?CODE_ROOM_INFO),
  %是否在房间里
  case State#player_state.is_room of
    true ->
      %表情常用语发送
      case game_assist:random_msg() of
        ?NIUNIU_DAILY_MSG -> State#player_state.game_pid ! {daily_msg};
        ?NIUNIU_EMOJI -> State#player_state.game_pid ! {emoji_msg};
        _ -> normal
      end;
    _ -> ok
  end,

  erlang:send_after(1000 * 60, self(), {timer_ten_mins}),
  {noreply, State};

%%定时任务
handle_info({timer_Mins_update}, State) ->
  try
    %是否在房间里
    case State#player_state.is_room of
      true -> ok;
      _ ->
        %玩家在大厅进入下一个房间
        player_assist:next_task(State)
    end,
    erlang:send_after(10000, self(), {timer_Mins_update})
  catch
    Class:Error:Stacktrace ->
      ?ERROR("player timer_Mins_update class:~p;error:~p;stacktrace:~p", [Class, Error, Stacktrace])
  end,
  {noreply, State};

%%其他消息转发给游戏进程
%加入牌桌:join_table；站起旁观:stand_look；旁观押注:look_bet；退出房间:out_room
%玩家下注:player_bet；常用语:daily_msg；emoji表情：emoji_msg
handle_info({game, Info}, State) ->
  ?INFO("player handle_info:~p", [Info]),
  GamePid = State#player_state.game_pid,
  case is_pid(GamePid) of
    true -> GamePid ! Info;
    _ ->
      ?DEBUG("GAMEPid is none! account:~p", [State#player_state.account]),
      ok
  end,
  {noreply, State};

%%任务完成
handle_info({task_complete}, State) ->
  %启动新账号关闭当前账号
  if
    State#player_state.is_room == true ->
      ?DEBUG("update palyer state logout ~p", [State#player_state.account]),
      erlang:put(?IS_LOGOUT, true),
      State#player_state.game_pid ! {checkout};
    true ->
      gen_server:cast(self(), {stop})
  end,
  case erlang:get(?LOGIN_ACCOUNT) of
    1 -> ok;
    _ ->
      case game_assist:task_limited(State#player_state.task_id) of
        true ->
          player_assist:new_account_login(State#player_state.task_id, State#player_state.is_look_bet);
        _ -> ok
      end,
      erlang:put(?LOGIN_ACCOUNT, 1)
  end,
  {noreply, State};

handle_info({Ref, badarg}, State) when is_reference(Ref) ->
  ?ERROR("badarg ref:~p,stateName:~p", [Ref, State]),
  {noreply, State};


handle_info(_Info, State) ->
  {noreply, State}.


%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
    State :: #state{}) -> term()).
terminate(_Reason, State) ->
  ?DEBUG("stop player account:~p", [State#player_state.account]),
  player_assist:player_logout(self(), State),
  %退出
  send_ws(?CODE_USER_LOGOUT),
  %关闭玩家连接
  ws_close(State#player_state.ws_connPid),
  ok.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
    Extra :: term()) ->
  {ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
  {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================


%%启动一个游戏进程
-spec add_game_child(GameId :: integer(), State :: #player_state{}) -> {ok, pid()}.
add_game_child(GameId, State) ->
  Process = list_to_atom("game_" ++ State#player_state.account ++ "_" ++ integer_to_list(GameId)),
  ?DEBUG("add_game_child process name :~p", [Process]),
  Child = {Process, {game_tbniuniu, start_link, [State]},
    temporary, infinity, worker, [game_tbniuniu]},
  ?DEBUG("add_game_child params:~p", [Child]),
  game_sup:addChild(Child).

-spec(init_webSocket(Token :: string(), Link_count :: string()) -> {ok, pid()}).
init_webSocket(Token, Link_count) ->
  WsHost = myshare:get_env(ws_host),
  WsPort = myshare:get_env(ws_port),
  {ok, ConnPid} = gun:open(WsHost, WsPort),
  gun:ws_upgrade(ConnPid, "/game/hall/" ++ Token ++ "/" ++ Link_count, []),
  {ok, ConnPid}.

-spec(ws_close(ConnPid :: pid()) -> none()).
ws_close(ConnPid) ->
  ?INFO("player logout"),
  gun:close(ConnPid).

%%玩家向大厅服务器发送消息
send_ws(Code) ->
  ?DEBUG("player send ws :~p", [Code]),
  Text =
    case Code of
      %获取用户信息
      ?CODE_USER_INFO ->
        jsx:encode(#{code => ?CODE_USER_INFO});
      %获取房间信息
      ?CODE_ROOM_INFO ->
        jsx:encode(#{code => ?CODE_ROOM_INFO, isNullPlayer => 0, type => 0, gameType => 1});
      ?CODE_USER_LOGOUT ->
        jsx:encode(#{code => ?CODE_USER_LOGOUT});
      ?NIUNIU_MY_INTEGRAL ->
        jsx:encode(#{code => ?NIUNIU_MY_INTEGRAL});
      _ -> ok
    end,
  case Text of
    ok -> ok;
    _ ->
      Frame = {text, binary_to_list(Text)},
      ConnPid = player_assist:ws_pid(),
      ?INFO("send msg data ~p;pid:~p", [Frame, ConnPid]),
      try
        gun:ws_send(ConnPid, Frame)
      catch
        Class:Error:Stacktrace ->
          ?ERROR("player send msg  class:~p,error:~p,stack:~p", [Class, Error, Stacktrace])
      end
  end.

check_task_overdue(TaskId) ->
  case game_assist:get_task_info(TaskId) of
    [] -> ok;
    #game_task{status = Status} ->
      case Status of
        ?TASK_RUN -> ok;
        _ -> gen_server:cast(self(), {stop})
      end
  end.


%%围牛排队不超过两个
weiniu_queue(_RoomId, _GameId, _Account) ->
  %排队数量
  false.
%%  QueueNum = player_assist:room_queue_number(RoomId),
%%  case GameId =:= ?WEI_NIU andalso QueueNum > 1 of
%%    true ->
%%      ?INFO("into room queue >1 account:~p;roomid:~p", [Account, RoomId]),
%%      game_assist:insert_player_into_room(Account, RoomId),
%%      true;
%%    _ ->
%%      false
%%  end.







